/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.soa.util.tccgl;

import com.lianzt.commondata.AbstractCommonData;
import com.lianzt.commondata.DataConvertFactory;
import com.lianzt.exception.InstanceDataException;
import com.lianzt.util.HttpConnect;
import com.lianzt.util.StringUtil;
import com.soa.exception.GlobalException;
import com.soa.redis.RedisTemplate;
import com.soa.redis.opt.RedisOpt;
import com.soa.util.SystemUtil;
import it.sauronsoftware.jave.AudioAttributes;
import it.sauronsoftware.jave.Encoder;
import it.sauronsoftware.jave.EncodingAttributes;
import it.sauronsoftware.jave.FFMPEGLocator;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.logging.Level;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import redis.clients.jedis.ShardedJedis;

/**
 * 微信相关工具
 *
 * @author zuotai
 */
@Component
public class WeixinUtil {

    @Resource
    private RedisTemplate redisTemplate;
    private final Logger log = LoggerFactory.getLogger(WeixinUtil.class);
    private final String WX_ACCESS_TOKEN_KEY = "tccgl_wx_access_token";
    private final String WX_JSAPI_TICKET_KEY = "tccgl_wx_jsapi_ticket";
    private final String base = "abcdefghijklmnopqrstuvwxyz0123456789";     //随机字符串生成器
    /**
     * 超时时间
     */
    protected final int TIMEOUT = 10000;
    /**
     * 用于生成微信临时二维码scene_idi值的序列
     */
    private final String WX_SCENE_ID_SEQ = "wx_scene_id_seq";
    /**
     * 微信临时二维码生成的请求json
     */
    private final String WX_SCENE_REQ_JSON = "{\"expire_seconds\": %d, \"action_name\": \"QR_SCENE\", \"action_info\": {\"scene\": {\"scene_id\": %d}}}";
    /**
     * 二维码生成接口
     */
    public final String QRCODE = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=";
    /**
     * 模板短信
     */
    public final String TMPLATE_MSG = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=";
    
    /***
     * 企业号发送消息
     */
    public final String QY_SEND_MSG = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s";
    
    /**
     * 获取 jsapi_ticket
     */
    public final String JSAPI_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=";
    /**
     * 获取用户增减数据：getusersummary
     */
    public final String GET_USER_SUMMARY = "https://api.weixin.qq.com/datacube/getusersummary?access_token=";
    /**
     * 获取累计用户数据：getusercumulate
     */
    public final String GET_USER_CUMULATE = "https://api.weixin.qq.com/datacube/getusercumulate?access_token=";
    /**
     * 下载多媒体信息https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
     */
    public final String DOWN_FILE = "https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token={}&media_id=";
    /**
     * 微信临时带参二维码在内存库中的前缀
     */
    public final String PRE_SCENE_KEY = "wx_scene_id_";
    /**
     * 微信临时带参二维码，对应内存库中事件的类型：绑定业务员
     */
    public final int EVENT_BIND_YG = 1;
    /**
     * 服务完成提醒，模板消息ID
     */
    public final String DCZP_NOTICE_ID = "DI_uzkN29sQIp7a8ZSYylRESmUva2d_MxbFLl7KHpLA";

    /*
     *
     * 汽车保险到期提醒 zwk
     */
    public final String BXDQ = "3xcIfRPU9dEi6AUi1FC2EQXv-UNTsAAhtS46F-MrQWs";
    /*
     * 服务消息提醒, 模板id zwk
     *
     */
    public final String NEW_TX_ID = "Yvbtj7WjBDAemlzFvP_JQ9aHv8HZfuriL5EiFS7ssUM";
    /**
     * 驾照年审提醒模板 zwk
     */
    public final String NEW_JZNJ = "Zcmt_--Mfbc914m7f2BYuYEE7zyMTcO4ee9SPjsa7o0";
    /**
     * 驾照清分模板 zwk
     */
    public final String NEW_WZMB = "ey6URdR2clYeZSm8IC6839iPcc1QHqyksKpbING-6ew";
    /**
     * 新违章提醒，模板消息ID
     */
    public final String NEW_VIO_ID = "KGLch8KQHEEmh2Ylr1uhcvw1oHkdgFGMUUg1UEFKMrs";
    /**
     * 责任认定结果，模板消息ID
     */
    public final String ZRPD_ID = "URKTS_sqqwLaErcOZu3-9gBEzJJYXhEjQ6myoi5UHe0";
    /**
     * 审核通过通知推送
     */
    public final String SFTG_ID = "z-P7TwBZTiJ6RKZPKj8Yk6o9yrXgSOKdR_2sbH9OdNE";
    /**
     * 审核不通过通知推送
     */
    public final String SHBTG_ID = "IVksM2-cLXSmTMYiSjA30qX1J8oclJvQ9wv5WmYW9L4";
    /**
     * 微信文件类型:音频
     */
    public final String MEDIA_VOICE = "voice";
    /**
     * 微信文件类型:图片
     */
    public final String MEDIA_IMAGE = "image";
    /**
     * 微信文件类型:缩略图
     */
    public final String MEDIA_THUMB = "thumb";
    private final Map<String, String> MEDIA_FIX = new HashMap<String, String>();

    private final int UPLOAD_TIMEOUT = 20000;        //上传超时时间


    /**
     * 上传照片
     * @param files
     * @throws 上传失败时抛出异常
     */
    public void uploadPic(String date, File... files) {
        if (files == null || files.length < 1) {
            return;
        }
        String url = SystemUtil.getSysConfig("upload_url") ;
        log.debug("开始上传文件，url={}，{} 个文件", url, files.length);
        PostMethod filePost = null;
        try {
            filePost = new PostMethod(url);
            FilePart[] parts = new FilePart[files.length];
            for (int i = 0; i < files.length; i++) {
                parts[i] = new FilePart(files[i].getName(), files[i]);
            }
            
            filePost.setRequestEntity(new MultipartRequestEntity(parts, filePost.getParams()));
            
            HttpClient client = new HttpClient();
            client.getHttpConnectionManager().getParams().setConnectionTimeout(UPLOAD_TIMEOUT);
            int status = client.executeMethod(filePost);
            String res = filePost.getResponseBodyAsString();
            if (status == HttpStatus.SC_OK) {
                // 返回成功
                if (!"success".equals(res.trim())) {
                    throw new GlobalException(306001, res);        //上传失败：!#!
                }
            } else {
                throw new GlobalException(306001, "照片库处理失败");        //上传失败：!#!
            }
        } catch (SocketTimeoutException e) {
            throw new GlobalException(306001, "连接照片库超时", e);        //上传失败：!#!
        } catch (IOException e) {
            throw new GlobalException(306001, "网络异常");
        } finally {
            if (filePost != null) {
                filePost.releaseConnection();
            }
        }
    }

    /**
     * 初始化
     */
    @PostConstruct
    private void init() {
        MEDIA_FIX.put(MEDIA_IMAGE, "jpg");
        MEDIA_FIX.put(MEDIA_THUMB, "jpg");
        MEDIA_FIX.put(MEDIA_VOICE, "mp3");
    }

    /**
     * 获取微信access token
     *
     * @return
     */
    public String getAccessToken() {
        String token = redisTemplate.get(WX_ACCESS_TOKEN_KEY);
        if (StringUtil.isNull(token)) {
            String appId = SystemUtil.getSysConfig("wx_corpid");
            String appSecret = SystemUtil.getSysConfig("wx_corpsecret");
            StringBuilder backJson = new StringBuilder();
            String urlStr = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid="+appId+"&corpsecret="+appSecret;
            URLConnection conn = null;
            InputStream is = null;
            try {
                log.info("获取access token url:{}", urlStr);
                URL url = new URL(urlStr);
                conn = url.openConnection();
                conn.setReadTimeout(12 * 1000);
                conn.connect();
                is = conn.getInputStream();
                int i = 0;
                byte[] bt = new byte[1024 * 10];

                while ((i = is.read(bt)) != -1) {
                    backJson.append(new String(bt, "utf-8"));
                }
                AbstractCommonData acd = DataConvertFactory.getInstanceByJson(backJson.toString());
                token = acd.getStringValue("access_token");
                if (StringUtil.isNull(token)) {
                    log.warn("获取access token返回错误码：" + acd.getStringValue("errcode") + ",内容：" + acd.getStringValue("errmsg"));
                } else {
                    redisTemplate.set(WX_ACCESS_TOKEN_KEY, acd.getIntValue("expires_in"), token);
                }
            } catch (IOException ex) {
                log.error("获取微信access token失败", ex);
            } catch (InstanceDataException ex) {
                log.error("构造access token的CommonData错误 ->" + backJson, ex);
            } finally {
                try {
                    is.close();
                } catch (Exception ex) {
                }
            }
        }
        log.info("返回token-->" + token);
        return token;
    }

    /**
     * 调用接口
     *
     * @param api
     * @param json
     * @return
     */
    public AbstractCommonData callApi(String api, AbstractCommonData json) {
        return callApi(api, DataConvertFactory.praseNormJson(json));
    }

    /**
     * 调用接口
     *
     * @param api
     * @param json
     * @return
     */
    public AbstractCommonData callApi(String api, String json) {
        if (StringUtil.isNull(json)) {
            return null;
        }
        URL url = null;
        URLConnection con = null;
        BufferedWriter bw = null;
        BufferedReader br = null;

        try {
            url = new URL(api + getAccessToken());
            con = url.openConnection();
            /**
             * 然后把连接设为输出模式。URLConnection通常作为输入来使用，比如下载一个Web页。
             * 通过把URLConnection设为输出，你可以把数据向你个Web页传送。下面是如何做：
             */
            con.setDoOutput(true);
            con.setConnectTimeout(TIMEOUT);
            con.setReadTimeout(TIMEOUT);
            con.addRequestProperty("Content-Type", "text/plain");
            /**
             * 最后，为了得到OutputStream，简单起见，把它约束在Writer并且放入POST信息中，例如： ...
             */
            bw = new BufferedWriter(new OutputStreamWriter(con.getOutputStream(), "UTF-8"));
            StringBuilder sb = new StringBuilder();
            sb.append(json);
            log.info("weixin api req : {}", json);
            bw.write(sb.toString());

            bw.flush();

            String tmpStr;
            sb = new StringBuilder();

            br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));
            while ((tmpStr = br.readLine()) != null) {
                sb.append(tmpStr);
            }
            log.info("weixin api res : {}", sb);
            AbstractCommonData res = DataConvertFactory.getInstanceByJson(sb.toString().trim());
            if (res.containsKey("errcode") && res.getIntValue("errcode") != 0) {
                //当errcode=40001时，可重新获取access token
                //异常
                throw new GlobalException(300004, res.getStringValue("errcode") + "-" + res.getStringValue("errmsg"));          //请求处理错误:!#!
            }
            return res;
        } catch (SocketTimeoutException e) {
            //超时
            throw new GlobalException(300001, e);      //请求wx-server超时
        } catch (IOException e) {
            //网络异常
            log.debug("网络异常:", e);
            throw new GlobalException(300002, e);      //wx-server连接异常
        } catch (InstanceDataException e) {
            //响应格式异常
            throw new GlobalException(300003, e);      //wx-server响应格式错误
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (Exception e) {
                    log.warn("关闭连接异常：" + e.toString());
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (Exception e) {
                    log.warn("关闭连接异常：" + e.toString());
                }
            }
        }
    }

    /**
     * 生成临时二维码
     *
     * @param expire 有效时间
     * @param sceneId 参数
     * @return 微信返回
     */
    public AbstractCommonData getQRsence(int expire, int sceneId) {
        String json = String.format(WX_SCENE_REQ_JSON, expire, sceneId);
        return callApi(QRCODE, json);
    }

    /**
     * 获取jsapi_ticket
     *
     * @return 出现异常时返回null
     */
    public String getJsapiTicket() {
        //先从内存库中取
        String ticket = redisTemplate.get(WX_JSAPI_TICKET_KEY);
        if (StringUtil.isNull(ticket)) {
            //如果取出的值为空,需要从微信服务端获取
            HttpConnect hc = null;
            try {
                hc = new HttpConnect(JSAPI_TICKET + getAccessToken(), "utf-8", TIMEOUT);
                String json = hc.receMsg();
                log.info("获取微信jsapi_ticket响应:{}", json);
                if (StringUtil.isNull(json)) {
                    log.warn("获取微信jsapi_ticket响应数据为空:");
                    return null;
                }
                AbstractCommonData res = DataConvertFactory.getInstanceByJson(json.trim());
                if (res.containsKey("errcode") && res.getIntValue("errcode") == 0) {
                    //收到响应后把jsapi_ticket放入redis
                    ticket = res.getStringValue("ticket");
                    redisTemplate.set(WX_JSAPI_TICKET_KEY, res.getIntValue("expires_in"), ticket);
                    return ticket;
                }
                log.warn("获取微信jsapi_ticket错误:{}", res.getStringValue("errmsg"));
                return null;
            } catch (InstanceDataException e) {
                log.warn("获取微信jsapi_ticket响应数据格式不为json:", e);
                return null;
            } catch (SocketTimeoutException e) {
                log.warn("获取微信jsapi_ticket超时:", e);
                return null;
            } catch (IOException e) {
                log.warn("获取微信jsapi_ticket网络异常:", e);
                return null;
            } finally {
                if (hc != null) {
                    hc.closeStream();
                }
            }
        }
        return ticket;
    }

    /**
     * 获取jsapi的签名包
     *
     * @param url /tccgl/wx/ 之后的内容
     * @return
     */
    public AbstractCommonData getJsapiSign(String url) {
        String signUrl = SystemUtil.getSysConfig("wx_js_qm") + url;
        log.debug("签名url:{}",signUrl);
        log.debug("原始url:{}",url);
        String ticket = getJsapiTicket();
        String noncestr = UUID.randomUUID().toString();
        String timestamp = Long.toString(System.currentTimeMillis() / 1000);
        String str = "jsapi_ticket=" + ticket + "&noncestr=" + noncestr + "&timestamp=" + timestamp + "&url=" + signUrl;
        log.debug("待签名字符串:{}", str);
        String signature = "";
//        try {
//            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
//            crypt.reset();
//            crypt.update(str.getBytes("UTF-8"));
//            signature = byteToHex(crypt.digest());
//        } catch (NoSuchAlgorithmException ex) {
//            java.util.logging.Logger.getLogger(WeixinUtil.class.getName()).log(Level.SEVERE, null, ex);
//        } catch (UnsupportedEncodingException ex) {
//            java.util.logging.Logger.getLogger(WeixinUtil.class.getName()).log(Level.SEVERE, null, ex);
//        }  
        signature = new SHA1().getDigestOfString(str.getBytes());
        signature = signature.toLowerCase();//转换成小写
        AbstractCommonData acd = DataConvertFactory.getInstanceEmpty();
        acd.putBooleanValue("debug", false);
        acd.putStringValue("appId", SystemUtil.getSysConfig("wx_corpid"));
        acd.putStringValue("timestamp", timestamp);
        acd.putStringValue("nonceStr", noncestr);
        acd.putStringValue("signature", signature);
        log.debug("响应签名包:{}", acd);
        return acd;
    }
    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash)
        {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    /**
     * 获取一定长度的随机字符串
     *
     * @param length 指定字符串长度
     * @return 一定长度的字符串
     */
    public String getRandomStringByLength(int length) {
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(base.charAt(random.nextInt(base.length())));
        }
        return sb.toString();
    }

    /**
     * 获取一个scene_id
     *
     * @return
     */
    public int getSceneId() {
        return redisTemplate.incr(WX_SCENE_ID_SEQ).intValue();
    }

    /**
     * 微信xml转data
     *
     * @param xml
     * @return
     */
    public AbstractCommonData xml2Data(String xml) throws DocumentException {
        Document document = new SAXReader().read(new StringReader(xml));
        Element root = document.getRootElement();
        if ("xml".equals(root.getName())) {
            Iterator<Element> iterator = root.elementIterator();
            AbstractCommonData acd = DataConvertFactory.getInstanceEmpty();
            while (iterator.hasNext()) {
                Element item = iterator.next();
                acd.putStringValue(item.getName(), item.getText());
            }
            return acd;
        } else {
            log.error("解析非法的微信xml");
            return null;
        }
    }

    /**
     * 下载多媒体文件,不同类型文件对应不同的目录,由nginx发布
     * @param type 文件类型:image, voice, video, thumb
     * @param mediaId
     */
    public void downFile(String type, String mediaId) {
        FileConnect fc = null;
        try {
            fc = new FileConnect(DOWN_FILE.replace("{}", getAccessToken()) + mediaId);
            if (MEDIA_VOICE.equals(type)) {       //如果是语音格式的，转为mp3
                final String path = SystemUtil.getSysConfig("media_dir") + File.separator + type;
                String fileName = mediaId + "." + MEDIA_FIX.get(type);
                fc.downFile(path, mediaId);     //保存时不加扩展名
                File sourceFile = new File(path, mediaId);
                File targetFile = new File(path, fileName);

                EncodingAttributes attr = new EncodingAttributes();
                AudioAttributes audio = new AudioAttributes();
                attr.setAudioAttributes(audio);
                attr.setFormat("mp3");
                Encoder encoder = new Encoder();
                try {
                    encoder.encode(sourceFile, new File(path, fileName), attr);
                } catch (Exception e) {
                    //此处一直报异常，但是mp3可以正常转换出来，所以用debug输出，不使用warn
                    log.debug("media_id:{}，格式转换异常：", mediaId, e);
                }
                if (targetFile.isFile()) {
                    sourceFile.deleteOnExit();
                }
            } else {
             fc.downFile(SystemUtil.getSysConfig("media_dir"), mediaId + "." + MEDIA_FIX.get(type));
            }
        } catch (IOException e) {
            log.debug("文件下载失败:", e);
        }
    }
    
    /***
     * 企业号发送消息
     * @param touser 成员ID列表
     * @param toparty  部门ID列表
     * @param totag  标签ID列表
     * @param msgtype  消息类型，此时固定为：text （支持消息型应用跟主页型应用）
     * @param agentid   企业应用的id，整型。可在应用的设置页面查看
     * @param content  消息内容，最长不超过2048个字节，注意：主页型应用推送的文本消息在微信端最多只显示20个字（包含中英文） 
     */
    public String sendMessage(String  touser,String toparty,String totag,String msgtype,int agentid,String content){
        String revice=null;
        //获取access_token
        String access_token = getAccessToken();
        String url = String.format(QY_SEND_MSG, access_token);
        String param = "{\"touser\":\"%s\",\"toparty\": \"%s\",\"totag\": \"%s\",\"msgtype\": \"%s\","
                        +"\"agentid\":\"%s\",\"text\": {\"content\": \"%s\"},\"safe\":0}"; 
        param= String.format(param, touser,toparty,totag,msgtype,agentid,content);
        log.debug("请求url：{}", url);
        log.debug("企业号发送消息请求数据：{}", param);
        BufferedWriter bw = null;
        BufferedReader br = null;
        try {
            URL conn = new URL(url);
            //打开连接
            URLConnection openConn = conn.openConnection();
            // 发送POST请求必须设置如下两行
            openConn.setDoOutput(true);
            openConn.setDoInput(true);
            openConn.setConnectTimeout(20000);
            openConn.setReadTimeout(20000);
            // 设置通用的请求属性
            openConn.setRequestProperty("accept", "*/*");
            openConn.setRequestProperty("connection", "Keep-Alive");
            openConn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            openConn.setConnectTimeout(20000);
            openConn.setReadTimeout(20000);
            // 发送请求参数
            bw = new BufferedWriter(new OutputStreamWriter(openConn.getOutputStream(), "UTF-8"));
            StringBuilder sb = new StringBuilder();
            sb.append(param);
            bw.write(sb.toString());
            bw.flush();
            String tmpStr;
            sb = new StringBuilder();
            br = new BufferedReader(new InputStreamReader(openConn.getInputStream(), "utf-8"));
            while ((tmpStr = br.readLine()) != null) {
                sb.append(tmpStr);
            }
            log.info("RETURN url={} res={}", url, sb);
            revice =  sb.toString();
        } catch (MalformedURLException ex) {
            java.util.logging.Logger.getLogger(WeixinUtil.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            java.util.logging.Logger.getLogger(WeixinUtil.class.getName()).log(Level.SEVERE, null, ex);
        }finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (Exception e) {
                    log.warn("关闭连接异常：" + e.toString());
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (Exception e) {
                    log.warn("关闭连接异常：" + e.toString());
                }
            }
        }
          return revice;
    }

    public static void main(String[] args) {
        String mediaId = "UhMkIm8_tjNypr25VcLggsCZCfKiwzt9dcED2Y3nMRuwf5VAdPYDHhTyAqdguKzA";
        final String path = "G:\\temp\\media";
        String fileName = mediaId + ".mp3";
        File sourceFile = new File(path, mediaId);

        EncodingAttributes attr = new EncodingAttributes();
        AudioAttributes audio = new AudioAttributes();
        audio.setCodec("libmp3lame");
        attr.setAudioAttributes(audio);
        attr.setFormat("mp3");
        Encoder encoder = new Encoder(new FFMPEGLocator() {
            @Override
            protected String getFFMPEGExecutablePath() {
                return path;
            }
        });
        try {
            encoder.encode(sourceFile, new File(path, fileName), attr);
            sourceFile.deleteOnExit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
